package com.occludens.admin.datamodel;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Date;

import org.apache.commons.codec.binary.Base64;
import org.json.JSONException;
import org.json.JSONObject;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Cache;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Subclass;
import com.googlecode.objectify.annotation.Unindex;


@Subclass(index=true)
@Cache
public class User extends AdminObjectDataModel {

	@Unindex private String passwordHash;
	@Unindex private String passwordSalt;
	@Index private String firstName;
	@Index private String lastName;
	@Index private String photoUrl;
	@Index private String birthdayMonth;
	@Index private String birthdayDay;
	@Index private String birthdayYear;
	@Index private String federated = "false";
	private Date created;
	private Date lastLogin;
	@Index private Key<Role>[] roles;

	protected User() {} // Mandatory for Objectify
	
	public User(JSONObject userInfo) throws JSONException {
		super((String)userInfo.get("verifiedEmail"));
		this.setFederated(true);
	}

	public User(String name, String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		super(name);
		PasswordAndSalt passwordAndSalt = new PasswordAndSalt(password);
		this.passwordHash = passwordAndSalt.getPasswordHash();
		this.passwordSalt = passwordAndSalt.getPasswordSalt();
		this.created = new Date();
	}

	/**
	 * Checks for the user
	 * 
	 * @param password unencrypted password 
	 * @return Returns true if the unencrypted password matches the stored hashed password.
	 * Returns false if the password does not match or the URF-8 encoding or SHA1 hashing is not supported	
	 */
	public boolean authenticate(String password) {

		byte[] bDigest = Base64.decodeBase64(this.passwordHash);
		byte[] bSalt = Base64.decodeBase64(this.passwordSalt);
		byte[] proposedDigest = null;
		try {
			proposedDigest = getHash(password, bSalt);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace(); //TODO add some log information
			return false;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace(); //TODO add some log information
			return false;
		}

		return Arrays.equals(proposedDigest, bDigest);
	}

	private class PasswordAndSalt {
		private String passwordHash;
		private String passwordSalt;

		/**
		 * @return passwordHash returns the SHA1 hashed, salted and Base64 encoded password
		 */
		public String getPasswordHash() {
			return passwordHash;
		}

		/**
		 * @return passwordSalt return the random generated Base 64 encoded salt
		 */
		public String getPasswordSalt() {
			return passwordSalt;
		}

		/**
		 * Creates an Object containing a SHA1 hashed, salted and Base64 encoded password string
		 * and Base 64 encoded salt string based on a unencrypted password 
		 * 
		 * @param password should be unencrypted 
		 * @throws NoSuchAlgorithmException if SHA1 hashing is not supported
		 * @throws UnsupportedEncodingException if UTF-8 encoding is not supported
		 */
		public PasswordAndSalt(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
			byte[] bSalt = new byte[8];
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			random.nextBytes(bSalt);
			byte[] bDigest = getHash(password,bSalt);
			this.passwordHash = Base64.encodeBase64String(bDigest);
			this.passwordSalt = Base64.encodeBase64String(bSalt);
		}
	};

	/**
	 * Creates an SHA-1 hash based on unencrypted password and salt
	 * 
	 * @param password
	 * @param salt
	 * @return SHA-1 hash based on unencrypted password and salt 
	 * @throws NoSuchAlgorithmException if SHA1 hashing is not supported
	 * @throws UnsupportedEncodingException if UTF-8 encoding is not supported
	 */
	private byte[] getHash(String password, byte[] salt) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		MessageDigest digest = MessageDigest.getInstance("SHA-1");
		digest.reset();
		digest.update(salt);
		byte[] input = digest.digest(password.getBytes("UTF-8"));
		digest.reset();
		input = digest.digest(input);
		return input;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getPhotoUrl() {
		return photoUrl;
	}

	public void setPhotoUrl(String photoUrl) {
		this.photoUrl = photoUrl;
	}

	public String getBirthdayMonth() {
		return birthdayMonth;
	}

	public void setBirthdayMonth(String birthdayMonth) {
		this.birthdayMonth = birthdayMonth;
	}

	public String getBirthdayDay() {
		return birthdayDay;
	}

	public void setBirthdayDay(String birthdayDay) {
		this.birthdayDay = birthdayDay;
	}

	public String getBirthdayYear() {
		return birthdayYear;
	}

	public void setBirthdayYear(String birthdayYear) {
		this.birthdayYear = birthdayYear;
	}


	public boolean isFederated() {
		return "true".equalsIgnoreCase(this.federated);
	}

	public void setFederated(boolean federated) {
		this.federated = Boolean.toString(federated);
	}
	
	public void loggedIn() {
		this.lastLogin = new Date();
	}

}
